package com.eastdigit.bpm.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

@RestController
public class ProcessInstanceHighlightsResourceProxy {

	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private RepositoryService repositoryService;

	@Autowired
	private HistoryService historyService;

	protected ObjectMapper objectMapper = new ObjectMapper();

	@RequestMapping(value = "/process-instance/{processInstanceId}/highlightsproxy", method = RequestMethod.GET, produces = "application/json")
	public ObjectNode getHighlighted(@PathVariable String processInstanceId) {

		ObjectNode responseJSON = objectMapper.createObjectNode();

		responseJSON.put("processInstanceId", processInstanceId);

		ArrayNode activitiesArray = objectMapper.createArrayNode();
		ArrayNode hisActivitiesArray = objectMapper.createArrayNode();
		ArrayNode flowsArray = objectMapper.createArrayNode();

		try {
			ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
			String processDefinitionId = "";
			if (processInstance != null) {
				processDefinitionId = processInstance.getProcessDefinitionId();
				List<String> highLightedActivities = runtimeService.getActiveActivityIds(processInstanceId);
				for (String activityId : highLightedActivities) {
					activitiesArray.add(activityId);
				}
			} else {
				HistoricProcessInstance hisProcessInstance = historyService
						.createHistoricProcessInstanceQuery()
						.processInstanceId(processInstanceId).singleResult();
				processDefinitionId = hisProcessInstance
						.getProcessDefinitionId();
			}
			
			responseJSON.put("processDefinitionId", processDefinitionId);
			
			List<HistoricActivityInstance> hisActInstances = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).list();
			for (HistoricActivityInstance historicActivityInstance : hisActInstances) {
				hisActivitiesArray.add(historicActivityInstance.getActivityId());
			}

			ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService
					.getProcessDefinition(processDefinitionId);
			
			LinkedList<HistoricActivityInstance> hisActInstList = new LinkedList<HistoricActivityInstance>();
			hisActInstList.addAll(hisActInstances);
			
			List<String> highLightedFlows = new ArrayList<String>();
			getHighlightedFlows(processDefinition.getActivities(), hisActInstList, highLightedFlows);
			for (String flow : highLightedFlows) {
				flowsArray.add(flow);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
		responseJSON.put("activities", activitiesArray);
		responseJSON.put("hisActivities", hisActivitiesArray);
		responseJSON.put("flows", flowsArray);

		return responseJSON;
	}

	/**
	 * getHighlightedFlows
	 * 
	 * code logic: 1. Loop all activities by id asc order; 2. Check each
	 * activity's outgoing transitions and eventBoundery outgoing transitions,
	 * if outgoing transitions's destination.id is in other executed
	 * activityIds, add this transition to highLightedFlows List; 3. But if
	 * activity is not a parallelGateway or inclusiveGateway, only choose the
	 * earliest flow.
	 * 
	 * @param activityList
	 * @param hisActInstList
	 * @param highLightedFlows
	 */
	private void getHighlightedFlows(List<ActivityImpl> activityList,
			LinkedList<HistoricActivityInstance> hisActInstList,
			List<String> highLightedFlows) {

		// check out startEvents in activityList
		List<ActivityImpl> startEventActList = new ArrayList<ActivityImpl>();
		Map<String, ActivityImpl> activityMap = new HashMap<String, ActivityImpl>(
				activityList.size());
		for (ActivityImpl activity : activityList) {

			activityMap.put(activity.getId(), activity);

			String actType = (String) activity.getProperty("type");
			if (actType != null
					&& actType.toLowerCase().indexOf("startevent") >= 0) {
				startEventActList.add(activity);
			}
		}

		// These codes is used to avoid a bug:
		// ACT-1728 If the process instance was started by a callActivity, it
		// will be not have the startEvent activity in ACT_HI_ACTINST table
		// Code logic:
		// Check the first activity if it is a startEvent, if not check out the
		// startEvent's highlight outgoing flow.
		HistoricActivityInstance firstHistActInst = hisActInstList.getFirst();
		String firstActType = (String) firstHistActInst.getActivityType();
		if (firstActType != null
				&& firstActType.toLowerCase().indexOf("startevent") < 0) {
			PvmTransition startTrans = getStartTransaction(startEventActList,
					firstHistActInst);
			if (startTrans != null) {
				highLightedFlows.add(startTrans.getId());
			}
		}

		while (!hisActInstList.isEmpty()) {
			HistoricActivityInstance histActInst = hisActInstList.removeFirst();
			ActivityImpl activity = activityMap
					.get(histActInst.getActivityId());
			if (activity != null) {
				boolean isParallel = false;
				String type = histActInst.getActivityType();
				if ("parallelGateway".equals(type)
						|| "inclusiveGateway".equals(type)) {
					isParallel = true;
				} else if ("subProcess".equals(histActInst.getActivityType())) {
					getHighlightedFlows(activity.getActivities(),
							hisActInstList, highLightedFlows);
				}

				List<PvmTransition> allOutgoingTrans = new ArrayList<PvmTransition>();
				allOutgoingTrans.addAll(activity.getOutgoingTransitions());
				allOutgoingTrans
						.addAll(getBoundaryEventOutgoingTransitions(activity));
				List<String> activityHighLightedFlowIds = getHighlightedFlows(
						allOutgoingTrans, hisActInstList, isParallel);
				highLightedFlows.addAll(activityHighLightedFlowIds);
			}
		}
	}

	/**
	 * Check out the outgoing transition connected to firstActInst from
	 * startEventActList
	 * 
	 * @param startEventActList
	 * @param firstActInst
	 * @return
	 */
	private PvmTransition getStartTransaction(
			List<ActivityImpl> startEventActList,
			HistoricActivityInstance firstActInst) {
		for (ActivityImpl startEventAct : startEventActList) {
			for (PvmTransition trans : startEventAct.getOutgoingTransitions()) {
				if (trans.getDestination().getId().equals(
						firstActInst.getActivityId())) {
					return trans;
				}
			}
		}
		return null;
	}

	/**
	 * getBoundaryEventOutgoingTransitions
	 * 
	 * @param activity
	 * @return
	 */
	private List<PvmTransition> getBoundaryEventOutgoingTransitions(
			ActivityImpl activity) {
		List<PvmTransition> boundaryTrans = new ArrayList<PvmTransition>();
		for (ActivityImpl subActivity : activity.getActivities()) {
			String type = (String) subActivity.getProperty("type");
			if (type != null && type.toLowerCase().indexOf("boundary") >= 0) {
				boundaryTrans.addAll(subActivity.getOutgoingTransitions());
			}
		}
		return boundaryTrans;
	}

	/**
	 * find out single activity's highlighted flowIds
	 * 
	 * @param activity
	 * @param hisActInstList
	 * @param isExclusive
	 *            if true only return one flowId(Such as exclusiveGateway,
	 *            BoundaryEvent On Task)
	 * @return
	 */
	private List<String> getHighlightedFlows(
			List<PvmTransition> pvmTransitionList,
			LinkedList<HistoricActivityInstance> hisActInstList,
			boolean isParallel) {

		List<String> highLightedFlowIds = new ArrayList<String>();

		PvmTransition earliestTrans = null;
		HistoricActivityInstance earliestHisActInst = null;

		for (PvmTransition pvmTransition : pvmTransitionList) {

			String destActId = pvmTransition.getDestination().getId();
			HistoricActivityInstance destHisActInst = findHisActInst(
					hisActInstList, destActId);
			if (destHisActInst != null) {
				if (isParallel) {
					highLightedFlowIds.add(pvmTransition.getId());
				} else if (earliestHisActInst == null
						|| (earliestHisActInst.getId().compareTo(
								destHisActInst.getId()) > 0)) {
					earliestTrans = pvmTransition;
					earliestHisActInst = destHisActInst;
				}
			}
		}

		if ((!isParallel) && earliestTrans != null) {
			highLightedFlowIds.add(earliestTrans.getId());
		}

		return highLightedFlowIds;
	}

	private HistoricActivityInstance findHisActInst(
			LinkedList<HistoricActivityInstance> hisActInstList, String actId) {
		for (HistoricActivityInstance hisActInst : hisActInstList) {
			if (hisActInst.getActivityId().equals(actId)) {
				return hisActInst;
			}
		}
		return null;
	}
}
